using System;
using System.IO;

using PickGold.Exceptions;

namespace PickGold.Zip
{
	public partial class ZipEntry
	{
		private int _readExtraDepth;
		private void ReadExtraField()
		{
			_readExtraDepth++;
			// workitem 8098: ok (restore)
			long posn = this.ArchiveStream.Position;
			this.ArchiveStream.Seek(this._RelativeOffsetOfLocalHeader, SeekOrigin.Begin);
			// workitem 10178
			PickGold.Zip.SharedUtilities.Workaround_Ladybug318918(this.ArchiveStream);

			byte[] block = new byte[30];
			this.ArchiveStream.Read(block, 0, block.Length);
			int i = 26;
			Int16 filenameLength = (short)(block[i++] + block[i++] * 256);
			Int16 extraFieldLength = (short)(block[i++] + block[i++] * 256);

			// workitem 8098: ok (relative)
			this.ArchiveStream.Seek(filenameLength, SeekOrigin.Current);
			// workitem 10178
			PickGold.Zip.SharedUtilities.Workaround_Ladybug318918(this.ArchiveStream);

			ProcessExtraField(this.ArchiveStream, extraFieldLength);

			// workitem 8098: ok (restore)
			this.ArchiveStream.Seek(posn, SeekOrigin.Begin);
			// workitem 10178
			PickGold.Zip.SharedUtilities.Workaround_Ladybug318918(this.ArchiveStream);
			_readExtraDepth--;
		}


		private static bool ReadHeader(ZipEntry ze, System.Text.Encoding defaultEncoding)
		{
			int bytesRead = 0;

			// change for workitem 8098
			ze._RelativeOffsetOfLocalHeader = ze.ArchiveStream.Position;

			int signature = PickGold.Zip.SharedUtilities.ReadEntrySignature(ze.ArchiveStream);
			bytesRead += 4;

			// Return false if this is not a local file header signature.
			if (ZipEntry.IsNotValidSig(signature))
			{
				// Getting "not a ZipEntry signature" is not always wrong or an error.
				// This will happen after the last entry in a zipfile.  In that case, we
				// expect to read :
				//    a ZipDirEntry signature (if a non-empty zip file) or
				//    a ZipConstants.EndOfCentralDirectorySignature.
				//
				// Anything else is a surprise.

				ze.ArchiveStream.Seek(-4, SeekOrigin.Current); // unread the signature
				// workitem 10178
				PickGold.Zip.SharedUtilities.Workaround_Ladybug318918(ze.ArchiveStream);
				if (ZipEntry.IsNotValidZipDirEntrySig(signature) && (signature != ZipConstants.EndOfCentralDirectorySignature))
				{
					throw new BadReadException(String.Format("  Bad signature (0x{0:X8}) at position  0x{1:X8}", signature, ze.ArchiveStream.Position));
				}
				return false;
			}

			byte[] block = new byte[26];
			int n = ze.ArchiveStream.Read(block, 0, block.Length);
			if (n != block.Length) return false;
			bytesRead += n;

			int i = 0;
			ze._VersionNeeded = (Int16)(block[i++] + block[i++] * 256);
			ze._BitField = (Int16)(block[i++] + block[i++] * 256);
			ze._CompressionMethod_FromZipFile = ze._CompressionMethod = (Int16)(block[i++] + block[i++] * 256);
			ze._TimeBlob = block[i++] + block[i++] * 256 + block[i++] * 256 * 256 + block[i++] * 256 * 256 * 256;
			// transform the time data into something usable (a DateTime)
			ze._LastModified = PickGold.Zip.SharedUtilities.PackedToDateTime(ze._TimeBlob);
			ze._timestamp |= ZipEntryTimestamp.DOS;

			if ((ze._BitField & 0x01) == 0x01)
			{
				ze._Encryption_FromZipFile = ze._Encryption = EncryptionAlgorithm.PkzipWeak; // this *may* change after processing the Extra field
				ze._sourceIsEncrypted = true;
			}

			// NB: if ((ze._BitField & 0x0008) != 0x0008), then the Compressed, uncompressed and
			// CRC values are not true values; the true values will follow the entry data.
			// But, regardless of the status of bit 3 in the bitfield, the slots for
			// the three amigos may contain marker values for ZIP64.  So we must read them.
			{
				ze._Crc32 = (Int32)(block[i++] + block[i++] * 256 + block[i++] * 256 * 256 + block[i++] * 256 * 256 * 256);
				ze._CompressedSize = (uint)(block[i++] + block[i++] * 256 + block[i++] * 256 * 256 + block[i++] * 256 * 256 * 256);
				ze._UncompressedSize = (uint)(block[i++] + block[i++] * 256 + block[i++] * 256 * 256 + block[i++] * 256 * 256 * 256);

				if ((uint)ze._CompressedSize == 0xFFFFFFFF ||
					(uint)ze._UncompressedSize == 0xFFFFFFFF)

					ze._InputUsesZip64 = true;
			}

			Int16 filenameLength = (short)(block[i++] + block[i++] * 256);
			Int16 extraFieldLength = (short)(block[i++] + block[i++] * 256);

			block = new byte[filenameLength];
			n = ze.ArchiveStream.Read(block, 0, block.Length);
			bytesRead += n;

			// if the UTF8 bit is set for this entry, override the
			// encoding the application requested.

			if ((ze._BitField & 0x0800) == 0x0800)
			{
				// workitem 12744
				ze.AlternateEncoding = System.Text.Encoding.UTF8;
				ze.AlternateEncodingUsage = ZipOption.Always;
			}

			// need to use this form of GetString() for .NET CF
			ze._FileNameInArchive = ze.AlternateEncoding.GetString(block, 0, block.Length);

			// workitem 6898
			if (ze._FileNameInArchive.EndsWith("/")) ze.MarkAsDirectory();

			bytesRead += ze.ProcessExtraField(ze.ArchiveStream, extraFieldLength);

			ze._LengthOfTrailer = 0;

			// workitem 6607 - don't read for directories
			// actually get the compressed size and CRC if necessary
			if (!ze._FileNameInArchive.EndsWith("/") && (ze._BitField & 0x0008) == 0x0008)
			{
				// This descriptor exists only if bit 3 of the general
				// purpose bit flag is set (see below).  It is byte aligned
				// and immediately follows the last byte of compressed data,
				// as well as any encryption trailer, as with AES.
				// This descriptor is used only when it was not possible to
				// seek in the output .ZIP file, e.g., when the output .ZIP file
				// was standard output or a non-seekable device.  For ZIP64(tm) format
				// archives, the compressed and uncompressed sizes are 8 bytes each.

				// workitem 8098: ok (restore)
				long posn = ze.ArchiveStream.Position;

				// Here, we're going to loop until we find a ZipEntryDataDescriptorSignature and
				// a consistent data record after that.   To be consistent, the data record must
				// indicate the length of the entry data.
				bool wantMore = true;
				long SizeOfDataRead = 0;
				int tries = 0;
				while (wantMore)
				{
					tries++;
					// We call the FindSignature shared routine to find the specified signature
					// in the already-opened zip archive, starting from the current cursor
					// position in that filestream.  If we cannot find the signature, then the
					// routine returns -1, and the ReadHeader() method returns false,
					// indicating we cannot read a legal entry header.  If we have found it,
					// then the FindSignature() method returns the number of bytes in the
					// stream we had to seek forward, to find the sig.  We need this to
					// determine if the zip entry is valid, later.

					if (ze._container.ZipFile != null)
						ze._container.ZipFile.OnReadBytes(ze);

					long d = PickGold.Zip.SharedUtilities.FindSignature(ze.ArchiveStream, ZipConstants.ZipEntryDataDescriptorSignature);
					if (d == -1) return false;

					// total size of data read (through all loops of this).
					SizeOfDataRead += d;

					if (ze._InputUsesZip64)
					{
						// read 1x 4-byte (CRC) and 2x 8-bytes (Compressed Size, Uncompressed Size)
						block = new byte[20];
						n = ze.ArchiveStream.Read(block, 0, block.Length);
						if (n != 20) return false;

						// do not increment bytesRead - it is for entry header only.
						// the data we have just read is a footer (falls after the file data)
						//bytesRead += n;

						i = 0;
						ze._Crc32 = (Int32)(block[i++] + block[i++] * 256 + block[i++] * 256 * 256 + block[i++] * 256 * 256 * 256);
						ze._CompressedSize = BitConverter.ToInt64(block, i);
						i += 8;
						ze._UncompressedSize = BitConverter.ToInt64(block, i);
						i += 8;

						ze._LengthOfTrailer += 24;  // bytes including sig, CRC, Comp and Uncomp sizes
					}
					else
					{
						// read 3x 4-byte fields (CRC, Compressed Size, Uncompressed Size)
						block = new byte[12];
						n = ze.ArchiveStream.Read(block, 0, block.Length);
						if (n != 12) return false;

						// do not increment bytesRead - it is for entry header only.
						// the data we have just read is a footer (falls after the file data)
						//bytesRead += n;

						i = 0;
						ze._Crc32 = (Int32)(block[i++] + block[i++] * 256 + block[i++] * 256 * 256 + block[i++] * 256 * 256 * 256);
						ze._CompressedSize = (uint)(block[i++] + block[i++] * 256 + block[i++] * 256 * 256 + block[i++] * 256 * 256 * 256);
						ze._UncompressedSize = (uint)(block[i++] + block[i++] * 256 + block[i++] * 256 * 256 + block[i++] * 256 * 256 * 256);

						ze._LengthOfTrailer += 16;  // bytes including sig, CRC, Comp and Uncomp sizes

					}

					wantMore = (SizeOfDataRead != ze._CompressedSize);

					if (wantMore)
					{
						// Seek back to un-read the last 12 bytes  - maybe THEY contain
						// the ZipEntryDataDescriptorSignature.
						// (12 bytes for the CRC, Comp and Uncomp size.)
						ze.ArchiveStream.Seek(-12, SeekOrigin.Current);
						// workitem 10178
						PickGold.Zip.SharedUtilities.Workaround_Ladybug318918(ze.ArchiveStream);

						// Adjust the size to account for the false signature read in
						// FindSignature().
						SizeOfDataRead += 4;
					}
				}

				// seek back to previous position, to prepare to read file data
				// workitem 8098: ok (restore)
				ze.ArchiveStream.Seek(posn, SeekOrigin.Begin);
				// workitem 10178
				PickGold.Zip.SharedUtilities.Workaround_Ladybug318918(ze.ArchiveStream);
			}

			ze._CompressedFileDataSize = ze._CompressedSize;


			// bit 0 set indicates that some kind of encryption is in use
			if ((ze._BitField & 0x01) == 0x01)
			{
#if AESCRYPTO
				if (ze.Encryption == EncryptionAlgorithm.WinZipAes128 ||
					ze.Encryption == EncryptionAlgorithm.WinZipAes256)
				{
					int bits = ZipEntry.GetKeyStrengthInBits(ze._Encryption_FromZipFile);
					// read in the WinZip AES metadata: salt + PV. 18 bytes for AES256. 10 bytes for AES128.
					ze._aesCrypto_forExtract = WinZipAesCrypto.ReadFromStream(null, bits, ze.ArchiveStream);
					bytesRead += ze._aesCrypto_forExtract.SizeOfEncryptionMetadata - 10; // MAC (follows crypto bytes)
					// according to WinZip, the CompressedSize includes the AES Crypto framing data.
					ze._CompressedFileDataSize -= ze._aesCrypto_forExtract.SizeOfEncryptionMetadata;
					ze._LengthOfTrailer += 10;  // MAC
				}
				else
#endif
				{
					// read in the header data for "weak" encryption
					ze._WeakEncryptionHeader = new byte[12];
					bytesRead += ZipEntry.ReadWeakEncryptionHeader(ze._archiveStream, ze._WeakEncryptionHeader);
					// decrease the filedata size by 12 bytes
					ze._CompressedFileDataSize -= 12;
				}
			}

			// Remember the size of the blob for this entry.
			// We also have the starting position in the stream for this entry.
			ze._LengthOfHeader = bytesRead;
			ze._TotalEntrySize = ze._LengthOfHeader + ze._CompressedFileDataSize + ze._LengthOfTrailer;


			// We've read in the regular entry header, the extra field, and any
			// encryption header.  The pointer in the file is now at the start of the
			// filedata, which is potentially compressed and encrypted.  Just ahead in
			// the file, there are _CompressedFileDataSize bytes of data, followed by
			// potentially a non-zero length trailer, consisting of optionally, some
			// encryption stuff (10 byte MAC for AES), and the bit-3 trailer (16 or 24
			// bytes).

			return true;
		}



		internal static int ReadWeakEncryptionHeader(Stream s, byte[] buffer)
		{
			// PKZIP encrypts the compressed data stream.  Encrypted files must
			// be decrypted before they can be extracted.

			// Each PKZIP-encrypted file has an extra 12 bytes stored at the start of the data
			// area defining the encryption header for that file.  The encryption header is
			// originally set to random values, and then itself encrypted, using three, 32-bit
			// keys.  The key values are initialized using the supplied encryption password.
			// After each byte is encrypted, the keys are then updated using pseudo-random
			// number generation techniques in combination with the same CRC-32 algorithm used
			// in PKZIP and implemented in the CRC32.cs module in this project.

			// read the 12-byte encryption header
			int additionalBytesRead = s.Read(buffer, 0, 12);
			if (additionalBytesRead != 12)
				throw new ZipException(String.Format("Unexpected end of data at position 0x{0:X8}", s.Position));

			return additionalBytesRead;
		}



		private static bool IsNotValidSig(int signature)
		{
			return (signature != ZipConstants.ZipEntrySignature);
		}


		/// <summary>
		///   Reads one <c>ZipEntry</c> from the given stream.  The content for
		///   the entry does not get decompressed or decrypted.  This method
		///   basically reads metadata, and seeks.
		/// </summary>
		/// <param name="zc">the ZipContainer this entry belongs to.</param>
		/// <param name="first">
		///   true of this is the first entry being read from the stream.
		/// </param>
		/// <returns>the <c>ZipEntry</c> read from the stream.</returns>
		internal static ZipEntry ReadEntry(ZipContainer zc, bool first)
		{
			ZipFile zf = zc.ZipFile;
			Stream s = zc.ReadStream;
			System.Text.Encoding defaultEncoding = zc.AlternateEncoding;
			ZipEntry entry = new ZipEntry();
			entry._Source = ZipEntrySource.ZipFile;
			entry._container = zc;
			entry._archiveStream = s;
			if (zf != null)
				zf.OnReadEntry(true, null);

			if (first) HandlePK00Prefix(s);

			// Read entry header, including any encryption header
			if (!ReadHeader(entry, defaultEncoding)) return null;

			// Store the position in the stream for this entry
			// change for workitem 8098
			entry.__FileDataPosition = entry.ArchiveStream.Position;

			// seek past the data without reading it. We will read on Extract()
			s.Seek(entry._CompressedFileDataSize + entry._LengthOfTrailer, SeekOrigin.Current);
			// workitem 10178
			PickGold.Zip.SharedUtilities.Workaround_Ladybug318918(s);

			// ReadHeader moves the file pointer to the end of the entry header,
			// as well as any encryption header.

			// CompressedFileDataSize includes:
			//   the maybe compressed, maybe encrypted file data
			//   the encryption trailer, if any
			//   the bit 3 descriptor, if any

			// workitem 5306
			// http://www.codeplex.com/DotNetZip/WorkItem/View.aspx?WorkItemId=5306
			HandleUnexpectedDataDescriptor(entry);

			if (zf != null)
			{
				zf.OnReadBytes(entry);
				zf.OnReadEntry(false, entry);
			}

			return entry;
		}


		internal static void HandlePK00Prefix(Stream s)
		{
			// in some cases, the zip file begins with "PK00".  This is a throwback and is rare,
			// but we handle it anyway. We do not change behavior based on it.
			uint datum = (uint)PickGold.Zip.SharedUtilities.ReadInt(s);
			if (datum != ZipConstants.PackedToRemovableMedia)
			{
				s.Seek(-4, SeekOrigin.Current); // unread the block
				// workitem 10178
				PickGold.Zip.SharedUtilities.Workaround_Ladybug318918(s);
			}
		}



		private static void HandleUnexpectedDataDescriptor(ZipEntry entry)
		{
			Stream s = entry.ArchiveStream;

			// In some cases, the "data descriptor" is present, without a signature, even when
			// bit 3 of the BitField is NOT SET.  This is the CRC, followed
			//    by the compressed length and the uncompressed length (4 bytes for each
			//    of those three elements).  Need to check that here.
			//
			uint datum = (uint)PickGold.Zip.SharedUtilities.ReadInt(s);
			if (datum == entry._Crc32)
			{
				int sz = PickGold.Zip.SharedUtilities.ReadInt(s);
				if (sz == entry._CompressedSize)
				{
					sz = PickGold.Zip.SharedUtilities.ReadInt(s);
					if (sz == entry._UncompressedSize)
					{
						// ignore everything and discard it.
					}
					else
					{
						s.Seek(-12, SeekOrigin.Current); // unread the three blocks

						// workitem 10178
						PickGold.Zip.SharedUtilities.Workaround_Ladybug318918(s);
					}
				}
				else
				{
					s.Seek(-8, SeekOrigin.Current); // unread the two blocks

					// workitem 10178
					PickGold.Zip.SharedUtilities.Workaround_Ladybug318918(s);
				}
			}
			else
			{
				s.Seek(-4, SeekOrigin.Current); // unread the block

				// workitem 10178
				PickGold.Zip.SharedUtilities.Workaround_Ladybug318918(s);
			}
		}


		/// <summary>
		///   Finds a particular segment in the given extra field.
		///   This is used when modifying a previously-generated
		///   extra field, in particular when removing the AES crypto
		///   segment in the extra field.
		/// </summary>
		static internal int FindExtraFieldSegment(byte[] extra, int offx, UInt16 targetHeaderId)
		{
			int j = offx;
			while (j + 3 < extra.Length)
			{
				UInt16 headerId = (UInt16)(extra[j++] + extra[j++] * 256);
				if (headerId == targetHeaderId) return j - 2;

				// else advance to next segment
				Int16 dataSize = (short)(extra[j++] + extra[j++] * 256);
				j += dataSize;
			}

			return -1;
		}


		/// <summary>
		///   At current cursor position in the stream, read the extra
		///   field, and set the properties on the ZipEntry instance
		///   appropriately.  This can be called when processing the
		///   Extra field in the Central Directory, or in the local
		///   header.
		/// </summary>
		internal int ProcessExtraField(Stream s, Int16 extraFieldLength)
		{
			int additionalBytesRead = 0;
			if (extraFieldLength > 0)
			{
				byte[] buffer = this._Extra = new byte[extraFieldLength];
				additionalBytesRead = s.Read(buffer, 0, buffer.Length);
				long posn = s.Position - additionalBytesRead;
				int j = 0;
				while (j + 3 < buffer.Length)
				{
					int start = j;
					UInt16 headerId = (UInt16)(buffer[j++] + buffer[j++] * 256);
					Int16 dataSize = (short)(buffer[j++] + buffer[j++] * 256);

					switch (headerId)
					{
						case 0x000a:  // NTFS ctime, atime, mtime
							j = ProcessExtraFieldWindowsTimes(buffer, j, dataSize, posn);
							break;

						case 0x5455:  // Unix ctime, atime, mtime
							j = ProcessExtraFieldUnixTimes(buffer, j, dataSize, posn);
							break;

						case 0x5855:  // Info-zip Extra field (outdated)
							// This is outdated, so the field is supported on
							// read only.
							j = ProcessExtraFieldInfoZipTimes(buffer, j, dataSize, posn);
							break;

						case 0x7855:  // Unix uid/gid
							// ignored. DotNetZip does not handle this field.
							break;

						case 0x7875:  // ??
							// ignored.  I could not find documentation on this field,
							// though it appears in some zip files.
							break;

						case 0x0001: // ZIP64
							j = ProcessExtraFieldZip64(buffer, j, dataSize, posn);
							break;

#if AESCRYPTO
						case 0x9901: // WinZip AES encryption is in use.  (workitem 6834)
							// we will handle this extra field only  if compressionmethod is 0x63
							j = ProcessExtraFieldWinZipAes(buffer, j, dataSize, posn);
							break;
#endif
						case 0x0017: // workitem 7968: handle PKWare Strong encryption header
							j = ProcessExtraFieldPkwareStrongEncryption(buffer, j);
							break;
					}

					// move to the next Header in the extra field
					j = start + dataSize + 4;
				}
			}
			return additionalBytesRead;
		}

		private int ProcessExtraFieldPkwareStrongEncryption(byte[] Buffer, int j)
		{
			//           Value     Size     Description
			//           -----     ----     -----------
			//           0x0017    2 bytes  Tag for this "extra" block type
			//           TSize     2 bytes  Size of data that follows
			//           Format    2 bytes  Format definition for this record
			//           AlgID     2 bytes  Encryption algorithm identifier
			//           Bitlen    2 bytes  Bit length of encryption key
			//           Flags     2 bytes  Processing flags
			//           CertData  TSize-8  Certificate decryption extra field data
			//                              (refer to the explanation for CertData
			//                               in the section describing the
			//                               Certificate Processing Method under
			//                               the Strong Encryption Specification)

			j += 2;
			_UnsupportedAlgorithmId = (UInt16)(Buffer[j++] + Buffer[j++] * 256);
			_Encryption_FromZipFile = _Encryption = EncryptionAlgorithm.Unsupported;

			// DotNetZip doesn't support this algorithm, but we don't need to throw
			// here.  we might just be reading the archive, which is fine.  We'll
			// need to throw if Extract() is called.

			return j;
		}


#if AESCRYPTO
		private int ProcessExtraFieldWinZipAes(byte[] buffer, int j, Int16 dataSize, long posn)
		{
			if (this._CompressionMethod == 0x0063)
			{
				if ((this._BitField & 0x01) != 0x01)
					throw new BadReadException(String.Format("  Inconsistent metadata at position 0x{0:X16}", posn));

				this._sourceIsEncrypted = true;

				//this._aesCrypto = new WinZipAesCrypto(this);
				// see spec at http://www.winzip.com/aes_info.htm
				if (dataSize != 7)
					throw new BadReadException(String.Format("  Inconsistent size (0x{0:X4}) in WinZip AES field at position 0x{1:X16}", dataSize, posn));

				this._WinZipAesMethod = BitConverter.ToInt16(buffer, j);
				j += 2;
				if (this._WinZipAesMethod != 0x01 && this._WinZipAesMethod != 0x02)
					throw new BadReadException(String.Format("  Unexpected vendor version number (0x{0:X4}) for WinZip AES metadata at position 0x{1:X16}",
						this._WinZipAesMethod, posn));

				Int16 vendorId = BitConverter.ToInt16(buffer, j);
				j += 2;
				if (vendorId != 0x4541)
					throw new BadReadException(String.Format("  Unexpected vendor ID (0x{0:X4}) for WinZip AES metadata at position 0x{1:X16}", vendorId, posn));

				int keystrength = (buffer[j] == 1) ? 128 : (buffer[j] == 3) ? 256 : -1;
				if (keystrength < 0)
					throw new BadReadException(String.Format("Invalid key strength ({0})", keystrength));

				_Encryption_FromZipFile = this._Encryption = (keystrength == 128)
					? EncryptionAlgorithm.WinZipAes128
					: EncryptionAlgorithm.WinZipAes256;

				j++;

				// set the actual compression method
				this._CompressionMethod_FromZipFile =
				this._CompressionMethod = BitConverter.ToInt16(buffer, j);
				j += 2; // for the next segment of the extra field
			}
			return j;
		}

#endif

		private delegate T Func<T>();

		private int ProcessExtraFieldZip64(byte[] buffer, int j, Int16 dataSize, long posn)
		{
			// The PKWare spec says that any of {UncompressedSize, CompressedSize,
			// RelativeOffset} exceeding 0xFFFFFFFF can lead to the ZIP64 header,
			// and the ZIP64 header may contain one or more of those.  If the
			// values are present, they will be found in the prescribed order.
			// There may also be a 4-byte "disk start number."
			// This means that the DataSize must be 28 bytes or less.

			this._InputUsesZip64 = true;

			// workitem 7941: check datasize before reading.
			if (dataSize > 28)
				throw new BadReadException(String.Format("  Inconsistent size (0x{0:X4}) for ZIP64 extra field at position 0x{1:X16}",
														 dataSize, posn));
			int remainingData = dataSize;

			var slurp = new Func<Int64>(() =>
			{
				if (remainingData < 8)
					throw new BadReadException(String.Format("  Missing data for ZIP64 extra field, position 0x{0:X16}", posn));
				var x = BitConverter.ToInt64(buffer, j);
				j += 8;
				remainingData -= 8;
				return x;
			});

			if (this._UncompressedSize == 0xFFFFFFFF)
				this._UncompressedSize = slurp();

			if (this._CompressedSize == 0xFFFFFFFF)
				this._CompressedSize = slurp();

			if (this._RelativeOffsetOfLocalHeader == 0xFFFFFFFF)
				this._RelativeOffsetOfLocalHeader = slurp();

			// Ignore anything else. Potentially there are 4 more bytes for the
			// disk start number.  DotNetZip currently doesn't handle multi-disk
			// archives.
			return j;
		}


		private int ProcessExtraFieldInfoZipTimes(byte[] buffer, int j, Int16 dataSize, long posn)
		{
			if (dataSize != 12 && dataSize != 8)
				throw new BadReadException(String.Format("  Unexpected size (0x{0:X4}) for InfoZip v1 extra field at position 0x{1:X16}", dataSize, posn));

			Int32 timet = BitConverter.ToInt32(buffer, j);
			this._Mtime = _unixEpoch.AddSeconds(timet);
			j += 4;

			timet = BitConverter.ToInt32(buffer, j);
			this._Atime = _unixEpoch.AddSeconds(timet);
			j += 4;

			this._Ctime = DateTime.UtcNow;

			_ntfsTimesAreSet = true;
			_timestamp |= ZipEntryTimestamp.InfoZip1; return j;
		}



		private int ProcessExtraFieldUnixTimes(byte[] buffer, int j, Int16 dataSize, long posn)
		{
			// The Unix filetimes are 32-bit unsigned integers,
			// storing seconds since Unix epoch.

			if (dataSize != 13 && dataSize != 9 && dataSize != 5)
				throw new BadReadException(String.Format("  Unexpected size (0x{0:X4}) for Extended Timestamp extra field at position 0x{1:X16}", dataSize, posn));

			int remainingData = dataSize;

			var slurp = new Func<DateTime>(() =>
			{
				Int32 timet = BitConverter.ToInt32(buffer, j);
				j += 4;
				remainingData -= 4;
				return _unixEpoch.AddSeconds(timet);
			});

			if (dataSize == 13 || _readExtraDepth > 0)
			{
				byte flag = buffer[j++];
				remainingData--;

				if ((flag & 0x0001) != 0 && remainingData >= 4)
					this._Mtime = slurp();

				this._Atime = ((flag & 0x0002) != 0 && remainingData >= 4)
					? slurp()
					: DateTime.UtcNow;

				this._Ctime = ((flag & 0x0004) != 0 && remainingData >= 4)
					? slurp()
					: DateTime.UtcNow;

				_timestamp |= ZipEntryTimestamp.Unix;
				_ntfsTimesAreSet = true;
				_emitUnixTimes = true;
			}
			else
				ReadExtraField(); // will recurse

			return j;
		}


		private int ProcessExtraFieldWindowsTimes(byte[] buffer, int j, Int16 dataSize, long posn)
		{
			// The NTFS filetimes are 64-bit unsigned integers, stored in Intel
			// (least significant byte first) byte order. They are expressed as the
			// number of 1.0E-07 seconds (1/10th microseconds!) past WinNT "epoch",
			// which is "01-Jan-1601 00:00:00 UTC".
			//
			// HeaderId   2 bytes    0x000a == NTFS stuff
			// Datasize   2 bytes    ?? (usually 32)
			// reserved   4 bytes    ??
			// timetag    2 bytes    0x0001 == time
			// size       2 bytes    24 == 8 bytes each for ctime, mtime, atime
			// mtime      8 bytes    win32 ticks since win32epoch
			// atime      8 bytes    win32 ticks since win32epoch
			// ctime      8 bytes    win32 ticks since win32epoch

			if (dataSize != 32)
				throw new BadReadException(String.Format("  Unexpected size (0x{0:X4}) for NTFS times extra field at position 0x{1:X16}", dataSize, posn));

			j += 4;  // reserved
			Int16 timetag = (Int16)(buffer[j] + buffer[j + 1] * 256);
			Int16 addlsize = (Int16)(buffer[j + 2] + buffer[j + 3] * 256);
			j += 4;  // tag and size

			if (timetag == 0x0001 && addlsize == 24)
			{
				Int64 z = BitConverter.ToInt64(buffer, j);
				this._Mtime = DateTime.FromFileTimeUtc(z);
				j += 8;

				// At this point the library *could* set the LastModified value
				// to coincide with the Mtime value.  In theory, they refer to
				// the same property of the file, and should be the same anyway,
				// allowing for differences in precision.  But they are
				// independent quantities in the zip archive, and this library
				// will keep them separate in the object model. There is no ill
				// effect from this, because as files are extracted, the
				// higher-precision value (Mtime) is used if it is present.
				// Apps may wish to compare the Mtime versus LastModified
				// values, but any difference when both are present is not
				// germaine to the correctness of the library. but note: when
				// explicitly setting either value, both are set. See the setter
				// for LastModified or the SetNtfsTimes() method.

				z = BitConverter.ToInt64(buffer, j);
				this._Atime = DateTime.FromFileTimeUtc(z);
				j += 8;

				z = BitConverter.ToInt64(buffer, j);
				this._Ctime = DateTime.FromFileTimeUtc(z);
				j += 8;

				_ntfsTimesAreSet = true;
				_timestamp |= ZipEntryTimestamp.Windows;
				_emitNtfsTimes = true;
			}
			return j;
		}


	}
}
